Certificate pinning#1065
Conversation
There was a problem hiding this comment.
Pull request overview
Adds native certificate pinning support for SDK-owned TLS traffic (internal HTTPS + WSS signaling) by introducing NetworkOptions/pinning rule configuration, enforcing pins/trust stores in the SDK’s IO HttpClient and WebSocket connection path, and documenting usage.
Changes:
- Introduces certificate pinning configuration types (
NetworkOptions,CertificatePinningOptions,CertificatePinningRule) andCertificatePinningException. - Adds pinning enforcement for SDK-owned HTTP(S) requests and WSS signaling on native platforms (with explicit “unsupported” behavior on Flutter web).
- Adds unit + IO tests and README documentation for pinning modes and rule semantics.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| test/support/certificate_pinning_test.dart | Unit tests for SPKI pin derivation, PEM/DER helpers, host matching, and rule merging semantics. |
| test/support/certificate_pinning_io_test.dart | Native IO tests validating fail-fast behavior (no HTTP/WSS bytes sent) and trust modes (leaf/trust store/combined). |
| test/mock/websocket_mock.dart | Extends websocket mock connector to capture networkOptions and simulate connect errors. |
| test/core/certificate_pinning_test.dart | Ensures signaling path passes networkOptions through and fast-fails on pinning exceptions. |
| README.md | Documents certificate pinning scope, rule behavior, and configuration examples. |
| pubspec.yaml | Adds asn1lib and crypto dependencies. |
| pubspec.lock | Locks new direct dependency versions (asn1lib, crypto now direct). |
| lib/src/support/websocket/web.dart | Plumbs networkOptions and throws on web when pinning is enabled. |
| lib/src/support/websocket/io.dart | Uses a custom IO HttpClient for WSS when pinning is enabled; rethrows pinning exceptions. |
| lib/src/support/websocket.dart | Extends connector API to accept networkOptions. |
| lib/src/support/region_url_provider.dart | Switches region settings fetch to SDK HTTP client and threads networkOptions. |
| lib/src/support/http_client/web.dart | Web implementation of SDK HTTP client; rejects pinning as unsupported. |
| lib/src/support/http_client/io.dart | IO implementation of SDK HTTP client with pinning-aware connectionFactory. |
| lib/src/support/http_client.dart | Adds SDK-owned sdkHttpGet/sdkHttpHead helpers using the platform client. |
| lib/src/support/certificate_pinning.dart | Implements SPKI pin extraction, PEM/DER utilities, host matching, and validation logic. |
| lib/src/options.dart | Adds NetworkOptions to RoomOptions and defines pinning configuration types. |
| lib/src/exceptions.dart | Adds CertificatePinningException for clear surfacing of pinning failures. |
| lib/src/core/signal_client.dart | Passes networkOptions into websocket + validation HTTP request; special-cases pinning failures. |
| lib/src/core/room.dart | Threads networkOptions into region provider and connection preparation HEAD requests. |
| .changes/add-certificate-pinning | Changelog entry for the new feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (proxyHost != null || proxyPort != null) { | ||
| throw UnsupportedError('Certificate pinning through HTTP proxies is not supported'); | ||
| } | ||
|
|
||
| if (!_isTlsScheme(url.scheme)) { | ||
| return io.Socket.startConnect(url.host, _portFor(url)); | ||
| } |
| final server = await io.SecureServerSocket.bind( | ||
| io.InternetAddress.loopbackIPv4, | ||
| 0, | ||
| context, | ||
| ); |
|
@copilot Does the recent commits address your findings ? |
Yes —
The latest runs for that commit also show |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
lib/src/core/room.dart:239
prepareConnectionswallows all exceptions and logs a generic warning without the underlying error. With certificate pinning enabled, this can hide actionable failures (e.g.,CertificatePinningException) and makes troubleshooting harder than other nearby code paths that log the error.
await sdkHttpHead(Uri.parse(toHttpUrl(regionUrl)), networkOptions: roomOptions.networkOptions);
logger.fine('prepared connection to ${regionUrl}');
}
} else {
await sdkHttpHead(Uri.parse(toHttpUrl(url)), networkOptions: roomOptions.networkOptions);
}
} catch (e) {
logger.warning('could not prepare connection');
}
| Iterable<List<int>> certificateDerCertificates(CertificateBytes certificate) sync* { | ||
| switch (certificate.encoding) { | ||
| case CertificateBytesEncoding.pem: | ||
| yield* _certificateDerCertificatesFromPem(certificate.bytes); | ||
| case CertificateBytesEncoding.der: | ||
| yield certificate.bytes; | ||
| } | ||
| } |
|
|
||
| ### Certificate pinning | ||
|
|
||
| Certificate pinning is available for native platforms through `RoomOptions.networkOptions`. It applies to SDK-owned WSS signaling and internal HTTPS requests. It does not apply to Flutter web, WebRTC media, TURN, or application-owned token endpoints. |
Summary
Adds native certificate pinning support for SDK-owned TLS traffic in the Flutter SDK.
CertificatePinningExceptionso pinning failures can fail fast and surface clearlyRule Behavior
Rules are selected by host. Exact hosts, single-label wildcards like
*.livekit.cloud, and*are supported. Emptyhostsapplies a rule to every SDK-owned TLS connection.All rules that match a host are applied. Within one check type, any configured value may match. Across check types, each configured type must pass.
That means:
primaryPinsandbackupPinsare both acceptedtrustedCertificateBytesreplaces platform trusted roots for that matching hostExample: SPKI Pins
Use this when possible. It is friendlier to certificate renewal because the pin follows the public key rather than the full leaf certificate.
Example: Exact Leaf Certificate
Use this when the app should trust one exact leaf certificate loaded from assets. Renewing or changing the leaf certificate requires shipping updated certificate bytes unless another configured check also allows the new certificate.
Example: Custom Trust Store
Use this for the asset-based Flutter pattern where the app ships a leaf, intermediate, or root certificate as trust material.
Example: Combine Modes
Checks can be combined. This requires the TLS chain to validate against the custom trust store and the peer certificate SPKI to match one configured pin.
Testing
Local verification:
flutter analyze --no-pub flutter test --no-pub --reporter compact